import { Argv } from 'yargs';
import { CLIHost, getCLIHost } from '../../spec-common/cliHost';
import { loadNativeModule } from '../../spec-common/commonUtils';
import { LogLevel, mapLogLevel } from '../../spec-utils/log';
import { getPackageConfig, PackageConfiguration } from '../../spec-utils/product';
import { UnpackArgv } from '../devContainersSpecCLI';
import { doFeaturesTestCommand } from './testCommandImpl';
import { runAsyncHandler } from '../utils';

// -- 'features test' command
export function featuresTestOptions(y: Argv) {
	return y
		.options({
			'project-folder': { type: 'string', alias: 'p', default: '.', description: 'Path to folder containing \'src\' and \'test\' sub-folders. This is likely the git root of the project.' },
			'features': { array: true, alias: 'f', describe: 'Feature(s) to test as space-separated parameters. Omit to run all tests.  Cannot be combined with \'--global-scenarios-only\'.' },
			'filter': { type: 'string', describe: 'Filter current tests to only run scenarios containing this string.  Cannot be combined with \'--skip-scenarios\'.' },
			'global-scenarios-only': { type: 'boolean', default: false, description: 'Run only scenario tests under \'tests/_global\' .  Cannot be combined with \'-f\'.' },
			'skip-scenarios': { type: 'boolean', default: false, description: 'Skip all \'scenario\' style tests.  Cannot be combined with \'--global--scenarios-only\'.' },
			'skip-autogenerated': { type: 'boolean', default: false, description: 'Skip all \'autogenerated\' style tests (test.sh).' },
			'skip-duplicated': { type: 'boolean', default: false, description: 'Skip all \'duplicate\' style tests (duplicate.sh).' },
			'permit-randomization': { type: 'boolean', default: false, description: 'Allow an element of randomness in test cases.' },
			'base-image': { type: 'string', alias: 'i', default: 'ubuntu:focal', description: 'Base Image. Not used for scenarios.' },  // TODO: Optionally replace 'scenario' configs with this value?
			'remote-user': { type: 'string', alias: 'u', describe: 'Remote user.  Not used for scenarios.', },  // TODO: Optionally replace 'scenario' configs with this value?
			'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level.' },
			'preserve-test-containers': { type: 'boolean', default: false, description: 'Do not remove test containers after running tests.' },
			'quiet': { type: 'boolean', alias: 'q', default: false, description: 'Quiets output' },
		})
		// DEPRECATED: Positional arguments don't play nice with the variadic/array --features option.
		// Pass target directory with '--project-folder' instead.
		// This will still continue to work, but any value provided by --project-folder will be preferred.
		// Omitting both will default to the current working directory.
		.deprecateOption('target', 'Use --project-folder instead')
		.positional('target', { type: 'string', default: '.', description: 'Path to folder containing \'src\' and \'test\' sub-folders.', })
		// Validation
		.check(argv => {
			if (argv['global-scenarios-only'] && argv['features']) {
				throw new Error('Cannot combine --global-scenarios-only and --features');
			}
			if (argv['skip-scenarios'] && argv['global-scenarios-only']) {
				throw new Error('Cannot combine --skip-scenarios and --global-scenarios-only');
			}
			if (argv['filter-scenarios'] && argv['skip-scenarios']) {
				throw new Error('Cannot combine --filter-scenarios and --skip-scenarios');
			}
			return true;

		});
}

export type FeaturesTestArgs = UnpackArgv<ReturnType<typeof featuresTestOptions>>;
export interface FeaturesTestCommandInput {
	cliHost: CLIHost;
	pkg: PackageConfiguration;
	collectionFolder: string;
	features?: string[];
	filter: string | undefined;
	globalScenariosOnly: boolean;
	skipScenarios: boolean;
	skipAutogenerated: boolean;
	skipDuplicateTest: boolean;
	permitRandomization: boolean;
	baseImage: string;
	remoteUser: string | undefined;
	logLevel: LogLevel;
	preserveTestContainers: boolean;
	quiet: boolean;
	disposables: (() => Promise<unknown> | undefined)[];
}

export function featuresTestHandler(args: FeaturesTestArgs) {
	runAsyncHandler(featuresTest.bind(null, args));
}

async function featuresTest({
	'base-image': baseImage,
	'target': collectionFolder_deprecated,
	'project-folder': collectionFolder,
	features,
	filter,
	'global-scenarios-only': globalScenariosOnly,
	'skip-scenarios': skipScenarios,
	'skip-autogenerated': skipAutogenerated,
	'skip-duplicated': skipDuplicateTest,
	'permit-randomization': permitRandomization,
	'remote-user': remoteUser,
	'log-level': inputLogLevel,
	'preserve-test-containers': preserveTestContainers,
	quiet,
}: FeaturesTestArgs) {
	const disposables: (() => Promise<unknown> | undefined)[] = [];
	const dispose = async () => {
		await Promise.all(disposables.map(d => d()));
	};

	const cwd = process.cwd();
	const cliHost = await getCLIHost(cwd, loadNativeModule, true);
	const pkg = getPackageConfig();

	const logLevel = mapLogLevel(inputLogLevel);

	// Prefer the new --project-folder option over the deprecated positional argument.
	const targetProject = collectionFolder !== '.' ? collectionFolder : collectionFolder_deprecated;

	const args: FeaturesTestCommandInput = {
		baseImage,
		cliHost,
		logLevel,
		quiet,
		pkg,
		collectionFolder: cliHost.path.resolve(targetProject),
		features: features ? (Array.isArray(features) ? features as string[] : [features]) : undefined,
		filter,
		globalScenariosOnly,
		skipScenarios,
		skipAutogenerated,
		skipDuplicateTest,
		permitRandomization,
		remoteUser,
		preserveTestContainers,
		disposables
	};

	const exitCode = await doFeaturesTestCommand(args);

	await dispose();
	process.exit(exitCode);
}
